#include "commands.h"

#include <iostream>
#include <sstream>

#include "../rendering.h"

#define CMD SScrCommand

CScrVariables ScrVariables;

bool IsNum(string s)
{
    bool bWasDot = false;
    for(Uint32 i = 0; i < s.length(); ++i)
    {
        if(s[i] == '.')
        {
            if(bWasDot) return false;
            else bWasDot = true;
        }
        else if(s[i] >= '0' && s[i] <= '9') continue;
        else return false;
    }
    return true;
}

float ToFloat(string s)
{
    stringstream stream(s);
    float f;
    stream >> f;
    return f;
}

void ScrParse(vector<string> sCommand)
{
    if(sCommand.size() <= 0) return;

    if(sCommand[0] == "NewShader")
    {
        CMD Command;
        Command.Type = SCT_NEWSHADER;

        if(sCommand.size()%2 == 1)
        {
            cout << "Niepoprawna liczba parametrow przy dodawaniu shadera.\n";
            return;
        }

        for(unsigned int i = 1; i < sCommand.size(); ++i)
        {
            if(i%2 == 0 && i > 1 && sCommand[i] != "VertexPath" && sCommand[i] != "FragmentPath" && sCommand[i] != "GeometryPath")
            {
                cout << "Niepoprawny typ shadera (" << sCommand[i] << ").\n";
                ++i;
                continue;
            }
            Command.sParams.push_back(sCommand[i]);
        }

        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "NewColor")
    {
        CMD Command;
        Command.Type = SCT_NEWCOLOR;

        for(Uint32 i = 1; i < sCommand.size();)
        {
            if(i>1)
            {
                if(sCommand[i] == "RGB_f")
                {
                    Command.sParams.push_back(sCommand[i]);
                    ++i;
                    if(IsNum(sCommand[i]) && IsNum(sCommand[i+1]) && IsNum(sCommand[i+2]))
                    {
                        Command.sParams.push_back(sCommand[i]);
                        Command.sParams.push_back(sCommand[i+1]);
                        Command.sParams.push_back(sCommand[i+2]);
                        i += 3;
                        continue;
                    }
                    else
                    {
                        cout << "Kolor (" << Command.sParams[0] << "): oczekiwano parametru liczbowego.\n";
                        ++i;
                        continue;
                    }
                }
                else
                {
                    cout << "Kolor nie ma takiego parametru (" << sCommand[i] << ").\n";
                    ++i;
                    continue;
                }
            }
            Command.sParams.push_back(sCommand[i]);
            ++i;
        }
        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "NewTexture")
    {
        CMD Command;
        Command.Type = SCT_NEWTEXTURE;

        Command.sParams.push_back(sCommand[1]);
        for(Uint32 i = 2; i < sCommand.size();)
        {
            if(sCommand[i] == "Path")
            {
                Command.sParams.push_back(sCommand[i]);
                if(i+1 == sCommand.size())
                {
                    cout << "Tekstura (" << sCommand[1] << "): oczekiwano sciezki do pliku.\n";
                    continue;
                }
                Command.sParams.push_back(sCommand[i+1]);
                i += 2;
                continue;
            }
            else
            {
                cout << "Tekstura nie ma takiego parametru (" << sCommand[i] << ").\n";
                ++i;
                continue;
            }
        }
        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "NewMaterial")
    {
        CMD Command;
        Command.Type = SCT_NEWMATERIAL;

        Command.sParams.push_back(sCommand[1]);
        for(Uint32 i = 2; i < sCommand.size();)
        {
            if(sCommand[i] == "Color")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;

                if(i == sCommand.size())
                {
                    cout << "Material (" << sCommand[1] << "): oczekiwano nazwy koloru.\n";
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                ++i;
                continue;
            }

            else if(sCommand[i] == "Texture")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;

                if(i == sCommand.size())
                {
                    cout << "Material (" << sCommand[1] << "): oczekiwano nazwy tekstury.\n";
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                ++i;
                continue;
            }
            else
            {
                cout << "Material nie ma takiego parametru (" << sCommand[i] << ").\n";
                ++i;
                continue;
            }
        }

        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "NewMesh")
    {
        CMD Command;
        Command.Type = SCT_NEWMESH;

        Command.sParams.push_back(sCommand[1]);
        for(Uint32 i = 2; i < sCommand.size();)
        {
            if(sCommand[i] == "Path")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;
                if(i == sCommand.size())
                {
                    cout << "Mesh (" << sCommand[1] << "): oczekiwano sciezki do pliku.\n";
                    ++i;
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                ++i;
                continue;
            }
            else
            {
                cout << "Mesh nie ma takiego parametru (" << sCommand[i] << ").\n";
                ++i;
                continue;
            }
        }

        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "NewRenderObject")
    {
        CMD Command;
        Command.Type = SCT_NEWRENDEROBJECT;

        Command.sParams.push_back(sCommand[1]);

        for(Uint32 i = 2; i < sCommand.size();)
        {
            if(sCommand[i] == "Shader")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;
                if(i == sCommand.size())
                {
                    cout << "RenderObject (" << sCommand[1] << "): oczekiwano nazwy shadera.\n";
                    ++i;
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                ++i;
            }
            else if(sCommand[i] == "Material")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;
                if(i == sCommand.size())
                {
                    cout << "RenderObject (" << sCommand[1] << "): oczekiwano nazwy materialu.\n";
                    ++i;
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                ++i;
            }
            else if(sCommand[i] == "Position")
            {
                Command.sParams.push_back(sCommand[i]);
                ++i;
                if(i == sCommand.size())
                {
                    cout << "RenderObject (" << sCommand[1] << "): oczekiwano wspolrzednych.\n";
                    ++i;
                    continue;
                }
                Command.sParams.push_back(sCommand[i]);
                Command.sParams.push_back(sCommand[i+1]);
                Command.sParams.push_back(sCommand[i+2]);
                i += 3;
            }
            else
            {
                cout << "RenderObject nie ma takiego parametru (" << sCommand[i] << ").\n";
                ++i;
                continue;
            }
        }

        ScrCommands.push_back(Command);
    }
    else if(sCommand[0] == "SendMesh")
    {
        CMD Command;
        Command.Type = SCT_SENDMESH;

        if(sCommand.size() != 3)
        {
            cout << "SendMesh: oczekiwano nazw obiektu Mesh i RenderObject.\n";
            return;
        }
        Command.sParams.push_back(sCommand[1]);
        Command.sParams.push_back(sCommand[2]);

        ScrCommands.push_back(Command);
    }
    else
    {
        cout << "Nie znam komendy " << sCommand[0] << ".\n";
    }
}

void ScrDoCommand(CMD Command)
{
    map<string, Color3*> &Colors = *(ScrVariables.pColors);
    map<string, ShaderProgram*> &Shaders = *(ScrVariables.pShaders);
    map<string, Texture*> &Textures = *(ScrVariables.pTextures);
    map<string, CMaterial*> &Materials = *(ScrVariables.pMaterials);
    map<string, Mesh*> &Meshes = *(ScrVariables.pMeshes);
    map<string, RenderObject*> &RenderObjects = *(ScrVariables.pRenderObjects);

    switch(Command.Type)
    {
        case SCT_NEWSHADER:
        {
            ShaderProgram *NewShader = new ShaderProgram;
            string sName = Command.sParams[0];

            for(unsigned int i = 1; i < Command.sParams.size(); i += 2)
            {
                if(Command.sParams[i] == "VertexPath")
                {
                    string sVertexPath = Command.sParams[i+1];

                    int v = NewShader->AddShader(GL_VERTEX_SHADER);
                    NewShader->LoadShader(v, sVertexPath);
                    NewShader->AttachShader(v);
                }
                else if(Command.sParams[i] == "FragmentPath")
                {
                    string sFragmentPath = Command.sParams[i+1];

                    int f = NewShader->AddShader(GL_FRAGMENT_SHADER);
                    NewShader->LoadShader(f, sFragmentPath);
                    NewShader->AttachShader(f);
                }
                else if(Command.sParams[i] == "GeometryPath")
                {
                    string sGeometryPath = Command.sParams[i+1];

                    int g = NewShader->AddShader(GL_GEOMETRY_SHADER);
                    NewShader->LoadShader(g, sGeometryPath);
                    NewShader->AttachShader(g);
                }
            }
            NewShader->Link();

            Shaders[sName] = NewShader;
            ScrVariables.ShaderNames.push_back(sName);
        }
        break;

        case SCT_NEWCOLOR:
        {
            Color3 *NewColor = new Color3;
            string sName = Command.sParams[0];

            for(Uint32 i = 1; i < Command.sParams.size();)
            {
                if(Command.sParams[i] == "RGB_f")
                {
                    float rgb[] =
                    {
                        ToFloat(Command.sParams[i+1]),
                        ToFloat(Command.sParams[i+2]),
                        ToFloat(Command.sParams[i+3])
                    };

                    NewColor->operator=(rgb);
                    i += 4;
                    continue;
                }
            }

            Colors[sName] = NewColor;
        }
        break;

        case SCT_NEWTEXTURE:
        {
            Texture *NewTexture = new Texture;
            string sName = Command.sParams[0];

            for(Uint32 i = 1; i < Command.sParams.size();)
            {
                if(Command.sParams[i] == "Path")
                {
                    NewTexture->Load(Command.sParams[i+1]);
                    i += 2;
                    continue;
                }
            }

            Textures[sName] = NewTexture;
        }
        break;

        case SCT_NEWMATERIAL:
        {
            CMaterial *NewMaterial = new CMaterial;
            string sName = Command.sParams[0];

            for(Uint32 i = 1; i < Command.sParams.size();)
            {
                if(Command.sParams[i] == "Texture")
                {
                    ++i;
                    NewMaterial->AddTexture(Textures[Command.sParams[i]]);
                    ++i;
                    continue;
                }
                else if(Command.sParams[i] == "Color")
                {
                    ++i;
                    NewMaterial->AddColor(Colors[Command.sParams[i]]);
                    ++i;
                    continue;
                }
            }

            Materials[sName] = NewMaterial;
        }
        break;

        case SCT_NEWMESH:
        {
            Mesh *NewMesh = new Mesh;
            string sName = Command.sParams[0];

            for(Uint32 i = 1; i < Command.sParams.size();)
            {
                if(Command.sParams[i] == "Path")
                {
                    ++i;
                    NewMesh->Load(Command.sParams[i]);
                    ++i;
                    continue;
                }
            }
            Meshes[sName] = NewMesh;
        }
        break;

        case SCT_NEWRENDEROBJECT:
        {
            RenderObject *NewRenderObject = new RenderObject;
            string sName = Command.sParams[0];

            for(Uint32 i = 1; i < Command.sParams.size();)
            {
                if(Command.sParams[i] == "Material")
                {
                    ++i;
                    NewRenderObject->SetMaterial(Materials[Command.sParams[i]]);
                    ++i;
                    continue;
                }
                if(Command.sParams[i] == "Shader")
                {
                    ++i;
                    NewRenderObject->SetShader(Shaders[Command.sParams[i]]);
                    ++i;
                    continue;
                }
                if(Command.sParams[i] == "Position")
                {
                    ++i;
                    Matrix4x4 *pMat = NewRenderObject->GetWorldMatrixPointer();
                    if(pMat == NULL)
                    {
                        i += 3;
                        continue;
                    }

                    NewRenderObject->GetWorldMatrixPointer()->Identity();
                    NewRenderObject->GetWorldMatrixPointer()->Translate(ToFloat(Command.sParams[i]),
                                                                        ToFloat(Command.sParams[i+1]),
                                                                        ToFloat(Command.sParams[i+2]));
                    i += 3;
                    continue;
                }
            }

            RenderObjects[sName] = NewRenderObject;
            ScrVariables.RenderObjectNames.push_back(sName);
        }
        break;

        case SCT_SENDMESH:
        {
            Meshes[Command.sParams[0]]->PassToRenderObj(RenderObjects[Command.sParams[1]]);
        }
        break;

        default: break;
    }
}

void ScrDoScript()
{
    for(Uint32 i = 0; i < ScrCommands.size(); ++i)
    {
        ScrDoCommand(ScrCommands[i]);
    }
}

void CScrVariables::AutoInit()
{
    pShaders = new map<string, ShaderProgram*>;
    pColors = new map<string, Color3*>;
    pTextures = new map<string, Texture*>;
    pMaterials = new map<string, CMaterial*>;
    pMeshes = new map<string, Mesh*>;
    pRenderObjects = new map<string, RenderObject*>;
}

#undef CMD
